using System;
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{
    #region 事件
    /// <summary>当接触地面时触发。</summary>
    public event Action OnEnterGround;
    /// <summary>当离开地面时触发。</summary>
    public event Action OnExitGround;
    /// <summary>当体型改变时触发，参数为改变了多少</summary>
    public event Action<float> OnScaleChange;
    /// <summary>当跳跃时触发，参数为是否为空中跳跃。</summary>
    public event Action<bool> OnJump;
    /// <summary>
    /// 最大生命值改变时触发
    /// </summary>
    public event Action<float> OnMaxHealthChange;
    /// <summary>
    /// 当前生命值改变时触发
    /// </summary>
    public event Action<float> OnCurrentHealthChange;
    #endregion
    #region 重力
    private Vector2 m_GravityDirection = new Vector2(0, -1);
    private float m_GravityMagnitude = 9.8f;
    private float m_GravityAbility = 0;
    public bool GravityAbility
    {
        get
        {
            return m_GravityAbility == 0;
        }
        set
        {
            if (value)
            {
                m_GravityAbility += 1;
            }
            else
            {
                m_GravityAbility -= 1;
            }
        }
    }
    #endregion
    #region 生命值相关
    private float m_CurrentHealth = 1000;
    private float m_MaxHealth = 1000;
    private float m_HealthRegeneration = 5f;
    /// <summary>
    /// 最大生命值
    /// </summary>
    public float MaxHealth
    {
        get { return m_MaxHealth; }
        set
        {
            float change = value - m_MaxHealth;
            m_MaxHealth = value;
            OnMaxHealthChange?.Invoke(change);
        }
    }
    /// <summary>
    /// 当前生命值
    /// </summary>
    public float CurrentHealth
    {
        get
        {
            return m_CurrentHealth;
        }
        set
        {
            float change = value - m_CurrentHealth;
            m_CurrentHealth = Mathf.Clamp(value, 0, m_MaxHealth);
            OnCurrentHealthChange?.Invoke(change);
        }
    }
    public float HealthRegeneration
    {
        get { return m_HealthRegeneration; }
        set { m_HealthRegeneration = value; }
    }
    #endregion
    #region 地面相关
    [Header("地面相关")]
    [InspectorName("最大地面角度")]
    [ReadOnly]
    [SerializeField]
    private float m_MaxGroundAngle = 46f;//最大地面倾斜角度，超过此角度的踩踏物将不被视作地面
    private float m_MaxGroundAngleCos;
    [InspectorName("基础最大移动速度")]
    public float maxMoveSpeed_Base = 4f;
    [InspectorName("当前最大移动速度")]
    public float maxMoveSpeed_Current = 4f;
    [InspectorName("当前速度")]
    [ReadOnly]
    [SerializeField]
    private Vector2 m_CurretnSpeed;
    [InspectorName("加速度")]
    public float acceleration_Ground = 12f;
    [InspectorName("运动学摩擦力")]
    public float kinematicFriction = 12;
    [InspectorName("在地面")]
    [SerializeField]
    [ReadOnly]
    private bool m_ContactGround = false;
    [InspectorName("地面方向")]
    [SerializeField]
    [ReadOnly]
    private Vector2 m_GroundDirection = Vector2.zero;
    [InspectorName("地面法线")]
    [SerializeField]
    [ReadOnly]
    private Vector2 m_GroundNormal = Vector2.zero;
    /// <summary>
    /// 玩家是否在地面
    /// </summary>
    public bool ContactGround
    {
        get { return m_ContactGround; }
        set
        {
            if (m_ContactGround != value)
            {
                m_ContactGround = value;
                if (m_ContactGround)
                {
                    OnEnterGround?.Invoke();
                }
                else
                {
                    OnExitGround?.Invoke();
                }
            }
        }
    }

    public float MaxGroundAngle
    {
        get { return m_MaxGroundAngle; }
        set
        {
            m_MaxGroundAngle = value;
            m_MaxGroundAngleCos = Mathf.Cos(m_MaxGroundAngle * Mathf.Deg2Rad);
        }
    }

    #endregion
    #region 跳跃相关
    [Header("跳跃相关")]
    [InspectorName("跳跃力")]
    public float JumpForce = 4.5f;
    [InspectorName("跳跃高度")]
    [ReadOnly]
    [SerializeField]
    private float m_JumpHeight;
    [InspectorName("最大空中跳跃次数")]
    public int maxAirJumpCount = 1;         //最大空中跳跃次数
    private int m_CurrentAirJumpCount = 0;  //当前已经进行的空中跳跃次数
    #endregion


    private int m_JumpAbility = 0;
    private int m_MoveAbility = 0;



    private int m_FacingDiraction = 1;//面朝方向
    private float m_Scale = 1f;//当前体型
    private readonly float m_MinScale = 0.1f;
    private readonly float m_MaxScale = 10f;
    private Vector3 m_InitialScale;

    private StateMachine<PlayerController> m_StateMachine;
    public BuffSystem<PlayerController> BuffSystem { get; private set; }
    public int CurrentAirJumpCount { get; private set; } = 0;
    public int MoveFactor { get; private set; } = 0;

    /// <summary>
    /// 跳跃能力的开关
    /// </summary>
    public bool JumpAbility
    {
        get
        { return m_JumpAbility == 0; }
        set
        {
            if (value)
            {
                m_JumpAbility += 1;
            }
            else
            {
                m_JumpAbility -= 1;
            }
        }
    }
    /// <summary>
    /// 移动能力的开关
    /// </summary>
    public bool MoveAbility
    {
        get
        { return m_MoveAbility == 0; }
        set
        {
            if (value)
            {
                m_MoveAbility += 1;
            }
            else
            {
                m_MoveAbility -= 1;
            }
        }
    }

    public event Action OnJumpButtonDown;



    #region 组件依赖
    public Rigidbody2D Rigidbody { get; private set; }
    public Animator Animator { get; private set; }
    private BoxCollider2D BoxCollider2D { get; set; }
    #endregion

    #region 状态池
    public StateBase<PlayerController> IdleState { get; private set; }
    public StateBase<PlayerController> MoveState { get; private set; }

    #endregion

    #region 动画控制器参数
    public int AnimatorParameter_Move { get; private set; }
    public int AnimatorParameter_Idle { get; private set; }
    public int AnimatorParameter_InAir { get; private set; }
    public int AnimatorParameter_Dead { get; private set; }
    public int AnimatorParameter_VelocityY { get; private set; }
    #endregion
    #region Public方法

    public void ChangeState(StateBase<PlayerController> state)
    {
        m_StateMachine.ChangeState(state);
    }
    /// <summary>
    /// 改变体型
    /// </summary>
    /// <param name="value">改变量</param>
    public void ChangeScale(float value)
    {
        float currentSacle = GetScale(true);
        m_Scale += value;
        float finalSacle = GetScale(true);
        if (currentSacle == finalSacle)
        {
            return;
        }
        else
        {
            float scaleChange = finalSacle - currentSacle;
            transform.localScale = new Vector3(m_InitialScale.x * finalSacle, m_InitialScale.y * finalSacle, m_InitialScale.z * finalSacle);
            transform.position += new Vector3(0, scaleChange / 2, 0);
            OnScaleChange?.Invoke(scaleChange);
        }
    }
    /// <summary>
    /// 获得体型
    /// </summary>
    /// <param name="real">是否获得最终被实际应用的体型</param>
    /// <returns></returns>
    public float GetScale(bool real)
    {
        if (real)
        {
            return Mathf.Clamp(m_Scale, m_MinScale, m_MaxScale);
        }
        else
        {
            return m_Scale;
        }
    }
    /// <summary>
    /// 跳跃
    /// </summary>
    public void Jump()
    {
        if (JumpAbility == false)
        {
            return;
        }
        if (ContactGround == false && m_CurrentAirJumpCount == maxAirJumpCount)
        {
            return;
        }

        Vector2 newVelocity;
        /*
         * 在地面跳跃的逻辑和在空中跳跃的逻辑是不一样的
         * 在空中跳跃时：
         * 如果y为负，直接抵消向下的力然后施加一个向上的力
         * 如果y为正，则将y补足至jumpforce
         * 如果y为正且大于jumpforce则什么也不用做
         * 在地面跳跃时：
         * 如果y为负，直接抵消向下的力然后施加一个向上的力
         * 如果y为正，则直接加上jumpforce，因为玩家要可以在斜坡上一边移动一边跳跃，此时由于延斜坡移动，y本来就是有一定的值的。
         */
        if (ContactGround)
        {
            if (Rigidbody.velocity.y <= 0)
            {
                newVelocity = new Vector2(Rigidbody.velocity.x, JumpForce);
            }
            else
            {
                newVelocity = Rigidbody.velocity + new Vector2(0, JumpForce);
            }
        }
        else
        {
            if (Rigidbody.velocity.y <= 0)
            {
                newVelocity = new Vector2(Rigidbody.velocity.x, JumpForce);
            }
            else
            {
                newVelocity = new Vector2(Rigidbody.velocity.x, MathF.Max(JumpForce, Rigidbody.velocity.y));
            }
            m_CurrentAirJumpCount++;
        }
        Rigidbody.velocity = newVelocity;
        OnJump?.Invoke(true);
    }

    /// <summary>
    /// 使玩家移动。
    /// 为了实现想要的手感，这个方法有点复杂，应该有性能优化空间，但我真的不想去想怎么优化了_(:з」∠)_
    /// </summary>
    public void Move()
    {
        if (MoveAbility == false)
        {
            return;
        }
        if (MoveFactor == 0)
        {
            return;
        }
        float currentX = Rigidbody.velocity.x;
        float currentY = Rigidbody.velocity.y;
        float newX;
        float newY;
        float acceleration = acceleration_Ground * Time.fixedDeltaTime;
        float Friction = kinematicFriction * Time.fixedDeltaTime;
        if (MoveFactor != m_FacingDiraction)
        {
            Flip();
        }
        //如果玩家要向右走，这时候有向左的速度，则摩擦力和加速度合并为加速度，让玩家更快转向。
        //反之也一样
        if ((currentX < 0 && MoveFactor == 1) || (currentX > 0 && MoveFactor == -1))
        {
            acceleration = (acceleration_Ground + kinematicFriction) * Time.fixedDeltaTime;
        }

        if (ContactGround)
        {
            //在地面上时，当玩家的当前速度大于最大移动速度时，摩擦力要使他减速，否则使用加速度加速
            if ((MoveFactor == 1 && Rigidbody.velocity.x > maxMoveSpeed_Current) || (MoveFactor == -1 && Rigidbody.velocity.x < maxMoveSpeed_Current * -1))
            {
                newX = Mathf.MoveTowards(currentX, maxMoveSpeed_Current * m_GroundDirection.x, Friction * Mathf.Abs(m_GroundDirection.x));
                newY = Mathf.MoveTowards(currentY, maxMoveSpeed_Current * m_GroundDirection.y, Friction * Mathf.Abs(m_GroundDirection.y));
            }
            else
            {
                newX = Mathf.MoveTowards(currentX, maxMoveSpeed_Current * m_GroundDirection.x, acceleration * Mathf.Abs(m_GroundDirection.x));
                newY = Mathf.MoveTowards(currentY, maxMoveSpeed_Current * m_GroundDirection.y, acceleration * Mathf.Abs(m_GroundDirection.y));
            }
        }
        else
        {
            //在空中时，无论当前速度如何都是用加速度来让当前速度趋向于最大移动速度
            newX = Mathf.MoveTowards(currentX, maxMoveSpeed_Current * MoveFactor, acceleration);
            newY = Rigidbody.velocity.y;
        }
        Rigidbody.velocity = new Vector2(newX, newY);
    }



    public void Idle()
    {
        if (Rigidbody.velocity.x == 0)
        {
            return;
        }
        float currentX = Rigidbody.velocity.x;
        float currentY = Rigidbody.velocity.y;
        float acceleration = kinematicFriction * Time.fixedDeltaTime;
        float newX = Mathf.MoveTowards(currentX, 0, acceleration);
        float newY;
        if (ContactGround)
        {
            newY = Mathf.MoveTowards(currentY, 0, acceleration);
        }
        else
        {
            newY = currentY;
        }
        Rigidbody.velocity = new Vector2(newX, newY);
    }

    public void OnMoveInput(InputAction.CallbackContext callbackContext)
    {
        MoveFactor = (int)callbackContext.ReadValue<float>();
    }

    public void OnJumpInput(InputAction.CallbackContext callbackContext)
    {
        if (callbackContext.started)
        {
            OnJumpButtonDown?.Invoke();
        }
    }
    #endregion

    #region Private方法
    private void Flip()
    {
        m_FacingDiraction *= -1;
        transform.Rotate(0f, 180f, 0f);
    }

    private void OnEnterGroundListener()
    {
        m_CurrentAirJumpCount = 0;
    }
    private void OnExitGroundListener()
    {
        m_GravityDirection = Vector2.down;
    }
    private void CheckContact(Collision2D collision)
    {
        Vector3 normal = Vector2.zero;
        bool TouchGround = false;
        for (int i = 0; i < collision.contactCount; i++)
        {
            normal = collision.GetContact(i).normal;
            if (normal.y >= m_MaxGroundAngleCos)
            {
                TouchGround = true;
                this.ContactGround = true;
                break;
            }
        }
        if (!TouchGround)
        {
            m_GroundDirection = Vector2.zero;
        }
        else
        {
            m_GroundDirection = Vector2.Perpendicular(normal) * m_FacingDiraction * -1;
            m_GroundNormal = normal;
            m_GravityDirection = -normal;
        }
    }
    WaitForSeconds m_WaitOneSecond = new WaitForSeconds(1);
    private IEnumerator HealthRegenerationCoroutine()
    {
        while (true)
        {
            yield return m_WaitOneSecond;
            CurrentHealth += HealthRegeneration;
        }
    }
    #endregion

    #region Unity方法
    private void OnValidate()
    {
        m_JumpHeight = Mathf.Pow(JumpForce, 2) / 2 / Mathf.Abs(Physics2D.gravity.y);
    }
    private void Reset()
    {
        m_JumpHeight = Mathf.Pow(JumpForce, 2) / 2 / Mathf.Abs(Physics2D.gravity.y);
    }
    private void Awake()
    {
        StartCoroutine(HealthRegenerationCoroutine());


        //事件系统初始化
        OnEnterGround += OnEnterGroundListener;
        OnExitGround += OnExitGroundListener;
        // 其他数值初始化
        m_InitialScale = transform.localScale;
        maxMoveSpeed_Current = maxMoveSpeed_Base;
        m_MaxGroundAngleCos = Mathf.Cos(m_MaxGroundAngle * Mathf.Deg2Rad);
        //获取组件依赖
        Rigidbody = GetComponent<Rigidbody2D>();
        Animator = GetComponent<Animator>();
        BoxCollider2D = GetComponent<BoxCollider2D>();
        //动画系统初始化
        AnimatorParameter_Move = Animator.StringToHash("Move");
        AnimatorParameter_Idle = Animator.StringToHash("Idle");
        AnimatorParameter_InAir = Animator.StringToHash("inAir");
        AnimatorParameter_Dead = Animator.StringToHash("dead");
        AnimatorParameter_VelocityY = Animator.StringToHash("velocityY");
        //状态机系统初始化
        IdleState = new Idle(this);
        MoveState = new Move(this);
        m_StateMachine = new StateMachine<PlayerController>(IdleState);
        //buff系统初始化
        BuffSystem = new BuffSystem<PlayerController>(this);

    }
    private void OnDrawGizmos()
    {
        Gizmos.DrawRay(transform.position, m_GroundDirection);
    }
    private void Update()
    {
        m_StateMachine.Update();
        BuffSystem.Update();
    }

    private void FixedUpdate()
    {
        m_StateMachine.FixedUpdate();
        BuffSystem.FixedUpdate();
        m_CurretnSpeed = Rigidbody.velocity;
        /*
         * 应用重力
         */
        if (GravityAbility && !ContactGround)
        {
            Rigidbody.velocity += m_GravityDirection * m_GravityMagnitude * Time.fixedDeltaTime;
        }

        //以下内容必须写在fixed update的最末尾
        if (m_GroundDirection == Vector2.zero)
        {
            ContactGround = false;
        }
        m_GroundDirection = Vector2.zero;
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        CheckContact(collision);
    }
    private void OnCollisionStay2D(Collision2D collision)
    {
        CheckContact(collision);
    }
    #endregion
}
